package org.carabiner.infinitest;

import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;

import junit.framework.Test;
import junit.framework.TestFailure;
import junit.framework.TestResult;
import junit.runner.BaseTestRunner;
import junit.runner.ReloadingTestSuiteLoader;

class JUnitRunner extends BaseTestRunner implements TestRunner
{
  private EventListenerList listenerList;
  private Set<String> testsRun;
  
  public JUnitRunner()
  {
    listenerList = new EventListenerList();
    testsRun = new HashSet<String>(); 
    setPreference("loading", "true");
    if (!(getLoader() instanceof ReloadingTestSuiteLoader))
      throw new IllegalStateException("Reloading class loaded not available!");        
  }
  
  protected void runFailed(String arg0)
  {
    System.out.println("Run Failed " + arg0);    
  }

  /**
   * msg is formatted as testNumber1(com.fakeco.fakeproduct.TestFakeProduct)
   */
  public void testEnded(String msg)
  {     
    testsRun.add(getMethodFromMessage(msg));    
    //System.out.println("Test ended " + msg);
  }

  public void testFailed(int arg0, Test test, Throwable arg2)
  {     
    //System.out.println("Test Failed " + arg0 + test.toString() + " Error: " + arg2.getMessage());
  }

  /**
   * msg is formatted as testNumber1(com.fakeco.fakeproduct.TestFakeProduct)
   */
  public void testStarted(String arg0)
  {
    //System.out.println("Test Started " + arg0);
  }
 
  public void runTest(String testClass)
  {
    Test suite = getTest(testClass);

    TestResult result = new TestResult();
    testsRun.clear();
    result.addListener(this);    
    suite.run(result);    
    for(Enumeration<TestFailure> e = result.failures(); e.hasMoreElements();)
    {
      TestFailure failure = e.nextElement();
      String methodName = getMethodFromFailure(failure);
      fireTestFailed(testClass, failure.exceptionMessage(), methodName);
      testsRun.remove(methodName);
    }    
    for(Enumeration<TestFailure> e = result.errors(); e.hasMoreElements();)
    {
      TestFailure error = e.nextElement();
      String methodName = getMethodFromFailure(error);
      fireTestError(getClassName(error), error.exceptionMessage(), error.thrownException(), methodName);
      testsRun.remove(methodName);
    }    
    for (String sucessfulTest : testsRun)
    {
      fireTestMethodSucceeded(testClass, sucessfulTest);
    }
    if (result.wasSuccessful())
      fireTestCaseSucceeded(testClass);    
  }

  private String getClassName(TestFailure failure)
  {
    return failure.failedTest().getClass().getName();
  }

  public void addTestStatusListener(TestStatusListener listener)
  {
    listenerList.add(TestStatusListener.class, listener);    
  }
  
  
  protected void fireTestFailed(final String testName, final String message, final String methodName)
  {
    try
    {
      SwingUtilities.invokeAndWait(new Runnable()
      {
        public void run()
        {
          TestStatusListener[] listeners = listenerList.getListeners(TestStatusListener.class);    
          for (int i = listeners.length - 1; i >= 0; i -= 1)
          {                  
            (listeners[i]).testFailed(new TestEvent(this, message, testName, methodName, null));      
          }
        }      
      });
    }
    catch (InterruptedException e)
    {
      throw new RuntimeException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new RuntimeException(e);
    }
  }

  private void fireTestError(final String testName, final String message, final Throwable throwable, final String methodName)
  {
    try
    {
      SwingUtilities.invokeAndWait(new Runnable()
      {
        public void run()
        {          
          TestStatusListener[] listeners = listenerList.getListeners(TestStatusListener.class);    
          for (int i = listeners.length - 1; i >= 0; i -= 1)
          {            
            (listeners[i]).testError(new TestEvent(this, message, testName, methodName, throwable));      
          }          
        }      
      });
    }
    catch (InterruptedException e)
    {
      throw new RuntimeException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new RuntimeException(e);
    }    
  }

  
  protected void fireTestMethodSucceeded(final String testName, final String methodName)
  {
    try
    {
      SwingUtilities.invokeAndWait(new Runnable()
      {
        public void run()
        {
          TestStatusListener[] listeners = listenerList.getListeners(TestStatusListener.class);    
          for (int i = listeners.length - 1; i >= 0; i -= 1)
          {                  
            (listeners[i]).testMethodSucceeded(new TestEvent(this, "Test Succeeded", testName, methodName, null));      
          }
        }      
      });
    }
    catch (InterruptedException e)
    {
      throw new RuntimeException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new RuntimeException(e);
    }    
  }

  protected void fireTestCaseSucceeded(final String testName)
  {        
    try
    {
      SwingUtilities.invokeAndWait(new Runnable()
      {
        public void run()
        {
          TestStatusListener[] listeners = listenerList.getListeners(TestStatusListener.class);    
          for (int i = listeners.length - 1; i >= 0; i -= 1)
          {                  
            (listeners[i]).testCaseSucceeded(new TestEvent(this, "Test Succeeded", testName, "", null));      
          }
        }      
      });
    }
    catch (InterruptedException e)
    {
      throw new RuntimeException(e);
    }
    catch (InvocationTargetException e)
    {
      throw new RuntimeException(e);
    }
  }
  
  public static String getMethodFromFailure(TestFailure failure)
  {
    String message = failure.toString();
    return getMethodFromMessage(message);    
  }

  private static String getMethodFromMessage(String message)
  {
    return message.substring(0, message.indexOf('('));
  }
  
}
